package it.unicam.ids.pvm.distributore;

import it.unicam.ids.pvm.data.AnnuncioDAO;
import it.unicam.ids.pvm.data.Configurazione;
import it.unicam.ids.pvm.data.DAOException;
import it.unicam.ids.pvm.data.DAOFactory;
import it.unicam.ids.pvm.postazione.Nastro;
import it.unicam.ids.pvm.postazione.PostazioneDiVendita;

import java.util.Date;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * La classe rappresenta il distributore automatico, 
 * adibito all'erogazione parallela dei prodotti.
 * Può essere doppio o singolo. Contiene i seguenti attributi:
 * stato, può essere in manutenzione o normale.
 * postazioni, le due postazioni di vendita.
 * gestoreRete, gestisce la notifica di informazioni e annunci.
 * annunci, annunci pubblicitari da visualizzare.
 * espositore, parte adibita all'esposizone dei prodotti.
 * tipo, può essere singolo o doppio.
 * 
 * @author Luca Iachini, Valerio Iachini, Alessandro Cardenà
 *
 */
public class DistributoreAutomatico {
	public enum Stato {
		MANUTENZIONE, NORMALE
	};

	private Stato stato;
	private PostazioneDiVendita[] postazioni;
	private GestoreRete gestoreRete;
	private Espositore espositore;
	private Lock lockLatoSinistro, lockLatoDestro;
	private TipoDistributore tipo = TipoDistributore.SINGOLO;
	private AnnuncioDAO annuncioDAO;

	/**
	 * Costruttore della classe.
	 * Inizializza il distributore definendone tipo, 
	 * postazioni, stato ed espositore. Crea la lista 
	 * degli annunci da visualizzare.
	 * @throws DAOException.
	 */
	
	public DistributoreAutomatico() throws DAOException {
		Configurazione conf = Configurazione.getInstance();
		if (conf.getTipoDistributore() != null)
			tipo = conf.getTipoDistributore();
		annuncioDAO = DAOFactory.getDAOFactory().getAnnuncioDAO();
		lockLatoSinistro = new ReentrantLock();
		lockLatoDestro = new ReentrantLock();
		postazioni = new PostazioneDiVendita[2];
		if (tipo == TipoDistributore.SINGOLO) {
			postazioni[0] = new PostazioneDiVendita(Lato.SINISTRO, this);
		} else {
			postazioni[0] = new PostazioneDiVendita(Lato.SINISTRO, this);
			postazioni[1] = new PostazioneDiVendita(Lato.DESTRO, this);
		}
		stato = Stato.NORMALE;
		espositore = Espositore.getInstance();
	}
	
	/**
	 * Il metodo consente di attivare l'erogazione del prodotto 
	 * selezionato, sincronizzando l'attività nel caso di 
	 * tipo doppio, per un acquisto parallelo.
	 * @param etichetta codice della casella contenete il 
	 * prodotto selezionato.
	 * @param lato lato del distributore il cui risiede 
	 * la casella in oggetto.
	 * @throws Exception.
	 */

	public void erogaProdotto(String etichetta, Lato lato) throws Exception {
		Casella casella = espositore.getCasella(etichetta);
		Nastro nastro = null;
		if(tipo==TipoDistributore.DOPPIO)
		 nastro = getPostazione(lato == Lato.SINISTRO ? Lato.DESTRO : Lato.SINISTRO ).getNastro();
		if (casella == null || (casella.getLato()==Lato.DESTRO && tipo == TipoDistributore.SINGOLO)) {
			throw new Exception("Casella non trovata");
		}
		Spirale spirale = casella.getSpirale();
		if (casella.getLato() != lato) {
			lockLatoDestro.lock();
			lockLatoSinistro.lock();
			nastro.muovi();
			spirale.giraSpirale();
			nastro.stop();
			lockLatoDestro.unlock();
			lockLatoSinistro.unlock();
		} else {
			if (lato == Lato.SINISTRO) {
				lockLatoSinistro.lock();
				spirale.giraSpirale();
				lockLatoSinistro.unlock();
			} else {
				lockLatoDestro.lock();
				spirale.giraSpirale();
				lockLatoDestro.unlock();
			}
		}

		Prodotto prodotto = casella.getProdotto();
		Date data = new Date();
		if (gestoreRete != null) {
			gestoreRete.inviaAcquisto(prodotto.getCodice(), data,
					prodotto.getPrezzo());
			if (casella.getQuantita() == 0) {
				gestoreRete.notificaProdottoFinito(prodotto, data);
			}
		}
	}
	
	/**
	 * Il metodo consente di regolare l'apertura dello 
	 * sportello del distributore, per la manutenzione.
	 * @throws Exception messaggio d'errore relativo 
	 * 						allo stato del distributore.
	 */

	public synchronized void apriSportello() throws Exception {
		if (this.stato == Stato.NORMALE) {
			stato = Stato.MANUTENZIONE;
			if(gestoreRete != null)
			gestoreRete.inviaStato(stato);
		} else
			throw new Exception("Distributore occupato");

	}
	
	/**
	 * Il metodo consente di regolare la chiusura dello 
	 * sportello del distributore, per l'usufruizione 
	 * normale delle proprie funzionalità.
	 */

	public synchronized void chiudiSportello() {
		stato = Stato.NORMALE;
		if(gestoreRete != null)
			gestoreRete.inviaStato(stato);
	}
	
	/**
	 * Il metodo restituisce la postazione 
	 * relativa al lato scelto.
	 * @param lato.
	 * @return postazione.
	 */

	public PostazioneDiVendita getPostazione(Lato lato) {
		int p = (lato == Lato.SINISTRO) ? 0 : 1;
		return postazioni[p];
	}

	/**
	 * Il metodo restituisce lo stato del distributore.
	 * @return stato.
	 */
	
	public Stato getStato() {
		return stato;
	}
	
	/**
	 * Il metodo modifica il gestore di rete.
	 * @param gestoreRete.
	 */

	public void setGestoreRete(GestoreRete gestoreRete) {
		this.gestoreRete = gestoreRete;
	}
	
	/**
	 * Il metodo restituisce il gestore di rete usato.
	 * @return gestoreRete.
	 */

	public GestoreRete getGestoreRete() {
		return gestoreRete;
	}
	
	/**
	 * Il metodo consente di aggiungere un nuovo annuncio 
	 * alla lista e alla relativa tabella nel database.
	 * @param annuncio annuncio da aggiungere.
	 * @throws DAOException.
	 */

	public void addAnnucio(Annuncio annuncio) throws DAOException {
		annuncioDAO.insert(annuncio);
	}
	
	/**
	 * Il metodo restituisce la lista di annunci visualizzati.
	 * @return annunci.
	 * @throws DAOException 
	 */

	public Annuncio[] getAnnunci() throws DAOException {
		return annuncioDAO.findAllAnnunci();
	}
	
	/**
	 * Il metodo permette di rimuovere un annuncio dalla lista 
	 * e dalla relativa tabella nel database.
	 * @param annuncio annuncio da eliminare.
	 * @throws DAOException.
	 */

	public void rimuoviAnnunicio(Annuncio annuncio) throws DAOException {
		annuncioDAO.delete(annuncio);
	}
	
	/**
	 * Il metodo restituisce il tipo del distributore 
	 * in uso.
	 * @return tipo.
	 */
	
	public TipoDistributore getTipoDistributore(){
		return tipo;
	}
	
}